﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;

namespace Noris.Schedule.Support.Core
{
	/// <summary>
	/// Matematický zlomek a veškeré počítání s ním
	/// </summary>
	public struct MathFraction : IComparable
	{
		#region KONSTRUKCE
		/// <summary>
		/// Veřejný konstruktor. Sestaví korektní zlomek ze zadaných hodnot.
		/// </summary>
		/// <param name="numerator"></param>
		/// <param name="denominator"></param>
		public MathFraction(int numerator, int denominator)
		{
			_Significator = (numerator < 0 ? -1 : 1) * (denominator < 0 ? -1 : 1);
			_Numerator = (numerator < 0 ? -numerator : numerator);
			_Denominator = (denominator < 0 ? -denominator : denominator);
			_ShortMe();
		}
		/// <summary>
		/// Privátní konstruktor. Používat výhradně tuto verzi, protože nevolá ShortMe, čímž by se kód zacyklil.
		/// </summary>
		/// <param name="numerator"></param>
		/// <param name="denominator"></param>
		/// <param name="isShorted"></param>
		private MathFraction(int numerator, int denominator, bool isShorted)
		{
			_Significator = (numerator < 0 ? -1 : 1) * (denominator < 0 ? -1 : 1);
			_Numerator = (numerator < 0 ? -numerator : numerator);
			_Denominator = (denominator < 0 ? -denominator : denominator);
		}
		/// <summary>
		/// Násobitel čitatele (znaménko), má hodnotu +1 nebo -1
		/// </summary>
		public int Significator { get { return _Significator; } }
		/// <summary>
		/// Znaménko +1 / -1
		/// </summary>
		private int _Significator;
		/// <summary>
		/// Čitatel (to je to horní číslo ve zlomku), může být záporné
		/// </summary>
		public int Numerator { get { return _Significator * _Numerator; } }
		/// <summary>
		/// Čitatel, vždy nezáporný
		/// </summary>
		private int _Numerator;
		/// <summary>
		/// Jmenovatel (to je to dolní číslo ve zlomku), nikdy není záporné
		/// </summary>
		public int Denominator { get { return _Denominator; } }
		/// <summary>
		/// Jmenovatel, vždy nezáporný
		/// </summary>
		private int _Denominator;
		/// <summary>
		/// Vizualizace
		/// </summary>
		/// <returns></returns>
		public override string  ToString()
		{
 			if (IsInfinitide) return "{infinite}";
			if (IsEmpty) return "0";
			return Numerator + "/" + Denominator;
		}
		public override bool Equals(object obj)
		{
			if (obj == null) return false;
			if (!(obj is MathFraction)) return false;
			MathFraction b = (MathFraction)obj;
			return (this._Significator == b._Significator
				&& this._Numerator == b._Numerator
				&& this._Denominator == b._Denominator);
		}
		public override int GetHashCode()
		{
			return _Significator ^ _Numerator ^ _Denominator;
		}
		#endregion
		#region PROPERTY
		/// <summary>
		/// true, pokud je zlomek prázdný (0/0)
		/// </summary>
		public bool IsEmpty { get { return (_Numerator == 0 && _Denominator == 0); } }
		/// <summary>
		/// true, pokud je zlomek nekonečný (x/0)
		/// </summary>
		public bool IsInfinitide { get { return (_Numerator != 0 && _Denominator == 0); } }
		/// <summary>
		/// Double hodnota zlomku (hodnota zlomku 3/8 = 0.125D)
		/// </summary>
		public double ValueD { get { return (_Denominator == 0 ? 0 : ((double)Numerator) / ((double)_Denominator)); } }
		/// <summary>
		/// Decimal hodnota zlomku (hodnota zlomku 3/8 = 0.125D)
		/// </summary>
		public decimal ValueM { get { return (_Denominator == 0 ? 0 : ((decimal)Numerator) / ((decimal)_Denominator)); } }
		/// <summary>
		/// Float hodnota zlomku (hodnota zlomku 3/8 = 0.125D)
		/// </summary>
		public float ValueF { get { return (_Denominator == 0 ? 0 : ((float)Numerator) / ((float)_Denominator)); } }
		/// <summary>
		/// Negativní hodnota tohoto zlomku (1/3 vrátí -1/3)
		/// </summary>
		public MathFraction Negative { get { return new MathFraction(-this.Numerator, this.Denominator, true); } }
		/// <summary>
		/// Převrácená hodnota tohoto zlomku (4/9 vrátí 9/4)
		/// </summary>
		public MathFraction Reciprocal { get { return new MathFraction(this.Denominator, this.Numerator, true); } }
		/// <summary>
		/// Vrátí zlomek 0/1
		/// </summary>
		public static MathFraction Zero { get { return new MathFraction(0, 1, true); } }
		/// <summary>
		/// Vrátí nekonečný zlomek 1/0
		/// </summary>
		private static MathFraction Infinitide { get { return new MathFraction(1, 0, true); } }
		/// <summary>
		/// Maximální přesnost, default 10,000 (1/10000), minimálně 10
		/// </summary>
		public static int MaxPrecission { get { return (_MaxPrecission >= 10 ? _MaxPrecission : 10000); } set { _MaxPrecission = value; } }
		private static int _MaxPrecission;
		#endregion
		#region OPERÁTORY A PŘETYPOVÁNÍ
		#region Porovnání
		/// <summary>
		/// Porovnání "je menší než"
		/// </summary>
		/// <param name="f1"></param>
		/// <param name="f2"></param>
		/// <returns></returns>
		public static bool operator <(MathFraction f1, MathFraction f2)
		{
			return (_Compare(f1, f2) < 0);
		}
		/// <summary>
		/// Porovnání "je menší než"
		/// </summary>
		/// <param name="f1"></param>
		/// <param name="f2"></param>
		/// <returns></returns>
		public static bool operator <=(MathFraction f1, MathFraction f2)
		{
			return (_Compare(f1, f2) <= 0);
		}
		/// <summary>
		/// Porovnání "je menší než"
		/// </summary>
		/// <param name="f1"></param>
		/// <param name="f2"></param>
		/// <returns></returns>
		public static bool operator ==(MathFraction f1, MathFraction f2)
		{
			return (_Compare(f1, f2) == 0);
		}
		/// <summary>
		/// Porovnání "je menší než"
		/// </summary>
		/// <param name="f1"></param>
		/// <param name="f2"></param>
		/// <returns></returns>
		public static bool operator !=(MathFraction f1, MathFraction f2)
		{
			return (_Compare(f1, f2) != 0);
		}
		/// <summary>
		/// Porovnání "je menší než"
		/// </summary>
		/// <param name="f1"></param>
		/// <param name="f2"></param>
		/// <returns></returns>
		public static bool operator >=(MathFraction f1, MathFraction f2)
		{
			return (_Compare(f1, f2) >= 0);
		}
		/// <summary>
		/// Porovnání "je menší než"
		/// </summary>
		/// <param name="f1"></param>
		/// <param name="f2"></param>
		/// <returns></returns>
		public static bool operator >(MathFraction f1, MathFraction f2)
		{
			return (_Compare(f1, f2) > 0);
		}
		/// <summary>
		/// Vrátí rozdíl porovnání f1 a f2
		/// </summary>
		/// <param name="f1"></param>
		/// <param name="f2"></param>
		/// <returns></returns>
		private static int _Compare(MathFraction f1, MathFraction f2)
		{
			MathFraction df = f1 - f2;
			return (df.Numerator.CompareTo(0));         // Porovná čitatele (z rozdílu obou zlomků) s nulou a vrátí výsledek porovnání.
		}
		#endregion
		#region Sčítání, odečítání
		/// <summary>
		/// Sčítání
		/// </summary>
		/// <param name="f1"></param>
		/// <param name="f2"></param>
		/// <returns></returns>
		public static MathFraction operator +(MathFraction f1, MathFraction f2)
		{
			return _Add(f1, f2);
		}
		/// <summary>
		/// Odečítání
		/// </summary>
		/// <param name="f1"></param>
		/// <param name="f2"></param>
		/// <returns></returns>
		public static MathFraction operator -(MathFraction f1, MathFraction f2)
		{
			return _Add(f1, f2.Negative);
		}
		#endregion
		#region Násobení, dělení
		/// <summary>
		/// Násobení
		/// </summary>
		/// <param name="f1"></param>
		/// <param name="f2"></param>
		/// <returns></returns>
		public static MathFraction operator *(MathFraction f1, MathFraction f2)
		{
			return _Mul(f1, f2);
		}
		/// <summary>
		/// Dělení
		/// </summary>
		/// <param name="f1"></param>
		/// <param name="f2"></param>
		/// <returns></returns>
		public static MathFraction operator /(MathFraction f1, MathFraction f2)
		{
			return _Mul(f1, f2.Reciprocal);
		}
		#endregion
		#region Implicitní přetypování
		public static implicit operator double(MathFraction f) { return f.ValueD; }
		public static implicit operator decimal(MathFraction f) { return f.ValueM; }
		public static implicit operator float(MathFraction f) { return f.ValueF; }
		public static implicit operator MathFraction(int f) { return new MathFraction(f,1); }
		public static implicit operator MathFraction(long f) { return new MathFraction((int)f, 1); }
		public static implicit operator MathFraction(short f) { return new MathFraction((int)f, 1); }
		#endregion
		#endregion
		#region PRIVÁTNÍ METODY
		/// <summary>
		/// Vrátí součet
		/// </summary>
		/// <param name="f1"></param>
		/// <param name="f2"></param>
		/// <returns></returns>
		private static MathFraction _Add(MathFraction f1, MathFraction f2)
		{
			// Od jednoduchých k složitějším případům:
			if (f1.IsInfinitide || f2.IsInfinitide) return Infinitide;
			if (f1.Numerator == 0) return f2;
			if (f2.Numerator == 0) return f1;
			MathFraction result;
			if (f1.Denominator == f2.Denominator)
			{
				result = new MathFraction(f1.Numerator + f2.Numerator, f1.Denominator, true);
			}
			else
			{
				int commonDenominator = GetMinCommonMultiplicand(f1.Denominator, f2.Denominator);
				int k1 = commonDenominator / f1.Denominator;
				int k2 = commonDenominator / f2.Denominator;
				result = new MathFraction(f1.Numerator * k1 + f2.Numerator * k2, commonDenominator, true);
			}
			result._ShortMe();
			return result;
		}
		/// <summary>
		/// Vrátí součin
		/// </summary>
		/// <param name="f1"></param>
		/// <param name="f2"></param>
		/// <returns></returns>
		private static MathFraction _Mul(MathFraction f1, MathFraction f2)
		{
			// Od jednoduchých k složitějším případům:
			if (f1.IsInfinitide) return Infinitide;
			if (f2.IsInfinitide) return MathFraction.Zero;
			if (f1.Numerator == 0) return MathFraction.Zero;
			if (f2.Numerator == 0) return MathFraction.Zero;
			MathFraction result = new MathFraction(f1.Numerator * f2.Numerator, f1.Denominator * f2.Denominator, true);
			result._ShortMe();
			return result;
		}
		/// <summary>
		/// Minimalizuje zlomek this.
		/// </summary>
		private void _ShortMe()
		{
			// Od jednoduchých k složitějším případům:
			if (this.Numerator == 0) this._SetZero();
			else if (this.IsInfinitide) this._SetInfinitide();
			else if (this.Numerator == this.Denominator) this._SetNumber(1);
			else if (-this.Numerator == this.Denominator) this._SetNumber(-1);
			else
			{
				int commonDivider = GetMaxCommonDivider(this.Numerator, this.Denominator);      // Nejvyšší společný dělitel obou čísel
				if (commonDivider != 1)
					this._SetNumber(this.Numerator / commonDivider, this.Denominator / commonDivider);
				if (this.Denominator > MaxPrecission)
				{
					int shorter = this.Denominator / MaxPrecission;
					if (shorter > 10)
					{
						this._Denominator = (int)(this._Denominator / shorter);
						this._Numerator = (int)(this._Numerator / shorter);
					}
				}
			}
		}
		/// <summary>
		/// Vloží 0
		/// </summary>
		private void _SetZero()
		{
			this._Significator = 1;
			this._Numerator = 0;
			this._Denominator = 1;
		}
		/// <summary>
		/// Vloží nekonečno
		/// </summary>
		private void _SetInfinitide()
		{
			this._Significator = 1;
			this._Numerator = 1;
			this._Denominator = 0;
		}
		/// <summary>
		/// Vloží celé číslo
		/// </summary>
		/// <param name="numerator"></param>
		private void _SetNumber(int numerator)
		{
			this._Significator = (numerator < 0 ? -1 : 1);
			this._Numerator = (numerator < 0 ? -numerator : numerator);
			this._Denominator = 1;
		}
		/// <summary>
		/// Vloží zlomek
		/// </summary>
		/// <param name="numerator"></param>
		/// <param name="denominator"></param>
		private void _SetNumber(int numerator, int denominator)
		{
			_Significator = (numerator < 0 ? -1 : 1) * (denominator < 0 ? -1 : 1);
			_Numerator = (numerator < 0 ? -numerator : numerator);
			_Denominator = (denominator < 0 ? -denominator : denominator);
		}
		/// <summary>
		/// Vrátí nejmenší společný činitel dvou čísel.
		/// Pokud je některé číslo záporné, vždy vrátí kladné číslo.
		/// </summary>
		/// <param name="d1"></param>
		/// <param name="d2"></param>
		/// <returns></returns>
		public static int GetMinCommonMultiplicand(int d1, int d2)
		{
			int a1 = (d1 < 0 ? -d1 : d1);
			int a2 = (d2 < 0 ? -d2 : d2);
			// Nula jako jeden z parametrů: vrátím ten druhý parametr (kladný):
			if (a1 == 0) return a2;
			if (a2 == 0) return a1;

			long max = d1 * d2;
			int t1 = a1;
			int t2 = a2;
			while (true)
			{
				if (t1 > max) return (int)max;
				if (t1 == t2) return t1;
				if (t1 < t2) t1 += a1;
				else if (t2 < t1) t2 += a2;
			}
		}
		/// <summary>
		/// Vrátí největší společný dělitel dvou čísel.
		/// Pokud je některé číslo záporné, vždy vrátí kladné číslo.
		/// </summary>
		/// <param name="d1"></param>
		/// <param name="d2"></param>
		/// <returns></returns>
		public static int GetMaxCommonDivider(int d1, int d2)
		{
			int a1 = (d1 < 0 ? -d1 : d1);
			int a2 = (d2 < 0 ? -d2 : d2);
			// Nula jako jeden z parametrů: vrátím ten druhý parametr (kladný):
			if (a1 == 0) return a2;
			if (a2 == 0) return a1;

			int div = 1;                    // Společný dělitel
			int wd = 2;                     // Průběžný testovací dělitel
			int t1 = a1;                    // Zadané číslo 1, bude se dělit až k hodnotě 1
			int t2 = a2;                    // Zadané číslo 2, bude se dělit až k hodnotě 1
			while (true)
			{
				if (t1 < wd || t2 < wd) break;          // Tento dílčí společný dělitel (wd) už je příliš veliký, některé číslo s ním dělit nelze.
				int r1, r2;
				int p1 = Math.DivRem(t1, wd, out r1);   // Dělím první číslo průběžným dělitelem
				int p2 = Math.DivRem(t2, wd, out r2);   // Dělím druhé číslo průběžným dělitelem
				if (r1 != 0 || r2 != 0)
				{	// Dělitel (wd) není společným dělitelem obou čísel, zkusíme další číslo jako dělitel:
					wd++;
					continue;
				}
				// Číslem (wd) lze dělit obě čísla:
				div = div * wd;             // Výsledný společný dělitel bude zahrnovat i tohoto dílčího společného dělitele
				t1 = p1;                    // Budeme hledat dalšího společného dělitele
				t2 = p2;                    //  pro obě vstupní čísla
			}
			return div;
		}
		/// <summary>
		/// Vrátí seznam dělitelů daného čísla. Nevrátí ani hodnotu 1 a ani samotné zadané číslo.
		/// Pro číslo 24 vrátí seznam: { 2; 3; 4; } ale nevrátí další dělitele { 6; 8; 12; }
		/// Pro číslo 35 vrátí seznam: { 5; 7; }
		/// Pro číslo 36 vrátí seznam: { 2; 3; 4; 6; } ale nevrátí další dělitele { 9; 12; 18; }
		/// </summary>
		/// <param name="dividend"></param>
		/// <returns></returns>
		public static Collection<int> GetDivisor(int dividend)
		{
			Collection<int> dividers = new Collection<int>();
			for (int testDivisor = 2; testDivisor < dividend; testDivisor++)        // testovací dělitelé
			{
				int remainder;                                                      // out zbytek po dělení
				int divisor = Math.DivRem(dividend, testDivisor, out remainder);    // hardwarové dělení s vyčíslením zbytku
				if (remainder == 0) dividers.Add(divisor);                          // dělení beze zbytku
				if (divisor <= testDivisor) break;                                  // podíl je menší nebo stejný jako dělitel => jsem za polovinou, skončím
			}
			return dividers;
		}
		#endregion
		#region IComparable Members

		int IComparable.CompareTo(object obj)
		{
			if (obj == null) return 1;
			if (!(obj is MathFraction)) return 1;
			MathFraction b = (MathFraction)obj;
			MathFraction c = this - b;                 // c je rozdíl (this - obj)
			return (c.Numerator.CompareTo(0));         // Porovná čitatele (z rozdílu obou zlomků) s nulou a vrátí výsledek porovnání.
		}

		#endregion
	}
	#region TESTY
	[Testable()]
	public class MathFractionTest : Noris.Schedule.Support.CommonTestBase
	{
		#region Testy správnosti výpočtu zlomků
		/// <summary>
		/// Testy správnosti výpočtu zlomků
		/// </summary>
		[TestPoint("Testy správnosti výpočtu zlomků", TestScopeType.Disabled)]
		public static void MathFractionBasicTest()
		{
			MathFraction f1, f2, f3, f4, f5, f6;
			f1 = new MathFraction(5, 9);
			f2 = new MathFraction(1, 3);

			f3 = f1 + f2;       //    5/9  +  1/3  =  8/9
			Assert.IsEqual(f3.Numerator, 8, "Zlomek má být 8/9, a je %0.", f3);
			Assert.IsEqual(f3.Denominator, 9, "Zlomek má být 8/9, a je %0.", f3);
			
			f4 = f3 - f1;       //    8/9  -  5/9  =  3/9  =  1/3
			Assert.IsEqual(f4.Numerator, 1, "Zlomek má být 1/3, a je %0.", f4);
			Assert.IsEqual(f4.Denominator, 3, "Zlomek má být 1/3, a je %0.", f4);

			f5 = f1 + 2;        //    5/9  +   2   =  23/9
			Assert.IsEqual(f5.Numerator, 23, "Zlomek má být 23/9, a je %0.", f5);
			Assert.IsEqual(f5.Denominator, 9, "Zlomek má být 23/9, a je %0.", f5);

			f6 = new MathFraction(24, 36);
			Assert.IsEqual(f6.Numerator, 2, "Zlomek má být 2/3, a je %0.", f6);
			Assert.IsEqual(f6.Denominator, 3, "Zlomek má být 2/3, a je %0.", f6);

			f1 = new MathFraction(15, 31);
			f2 = new MathFraction(1, 2);
			f3 = f1 * f2;       // 15/31  *  1/2  =  15/62
			Assert.IsEqual(f3.Numerator, 15, "Zlomek má být 15/62, a je %0.", f3);
			Assert.IsEqual(f3.Denominator, 62, "Zlomek má být 15/62, a je %0.", f3);

			f4 = f3 * 4;        // 15/62  *   4   =  60/62 = 30/31
			Assert.IsEqual(f4.Numerator, 30, "Zlomek má být 30/31, a je %0.", f4);
			Assert.IsEqual(f4.Denominator, 31, "Zlomek má být 30/31, a je %0.", f4);

			f5 = f4 + new MathFraction(2, 62);       // 30/31 + (2/62 = 1/31) = 31/31 = 1/1
			Assert.IsEqual(f5.Numerator, 1, "Zlomek má být 1/1, a je %0.", f5);
			Assert.IsEqual(f5.Denominator, 1, "Zlomek má být 1/1, a je %0.", f5);

			f6 = f3 / 5;        // 15/62  /  5  =  3 / 62
			Assert.IsEqual(f6.Numerator, 3, "Zlomek má být 3/62, a je %0.", f6);
			Assert.IsEqual(f6.Denominator, 62, "Zlomek má být 3/62, a je %0.", f6);

			// Klasický test:
			f1 = new MathFraction(1, 3);
			f3 = f1 + f1 + f1;
			Assert.IsEqual(f3.Numerator, 1, "Zlomek má být 1/1, a je %0.", f3);
			Assert.IsEqual(f3.Denominator, 1, "Zlomek má být 1/1, a je %0.", f3);
			Assert.IsEqual(f3.ValueM, 1M, "Zlomek má být 1.0000, a je %0.", f3.ValueM);

			// Záporná čísla:
			f1 = new MathFraction(-3, 4);
			f3 = 5 * f1 + new MathFraction(2, 8) - 3;        // 5 * (-3/4) + (2/8) - 3 = -6,5 = -13/2
			Assert.IsEqual(f3.Numerator, -13, "Zlomek má být -13/2, a je %0.", f3);
			Assert.IsEqual(f3.Denominator, 2, "Zlomek má být -13/2, a je %0.", f3);


			// Komparátory:
			bool e1, e2, e3, e4, e5;
			f1 = new MathFraction(15, 31);    // 0,48387096774193548387096774193548
			f2 = new MathFraction(1, 2);      // 0,50000000000000000000000000000000
			f3 = new MathFraction(16, 31);    // 0,51612903225806451612903225806452
			f4 = new MathFraction(3, 6);      // 0,50000000000000000000000000000000
			e1 = (f1 < f2);
			Assert.IsTrue(e1, "Zlomek %0 je menší než %1, ale porovnání si myslí něco jiného.", f1, f2);
			e2 = (f2 == f4);
			Assert.IsTrue(e2, "Zlomek %0 je shodný jako  %1, ale porovnání si myslí něco jiného.", f2, f4);
			e3 = (f3 > f4);
			Assert.IsTrue(e3, "Zlomek %0 je větší než %1, ale porovnání si myslí něco jiného.", f3, f4);
		}
		#endregion
		#region Testy rychlosti výpočtu zlomků
		/// <summary>
		/// Testy rychlosti výpočtu zlomků
		/// </summary>
		[TestPoint("Testy rychlosti výpočtu zlomků", TestScopeType.Disabled)]
		public static string MathFractionTimeTest()
		{
			string result = "";
			string eol = "\r\n";

			result += MathFractionTimeTestOne(1000, 15, 1) + eol;
			result += MathFractionTimeTestOne(1000, 15, 2) + eol;
			result += MathFractionTimeTestOne(10000, 15, 1) + eol;
			result += MathFractionTimeTestOne(10000, 15, 2) + eol;
			result += MathFractionTimeTestOne(100, 200, 1) + eol;
			result += MathFractionTimeTestOne(100, 200, 2) + eol;
			result += MathFractionTimeTestOne(1000, 200, 1) + eol;
			result += MathFractionTimeTestOne(1000, 200, 2) + eol;

			return result;
		}

		private static string MathFractionTimeTestOne(int maxPrecission, int loops, int mode)
		{
			MathFraction.MaxPrecission = maxPrecission;
			MathFraction fs, f1;
			double ds;
			string line = "";
			StopWatchStart();
			switch (mode)
			{
				case 1:
					// Sčítání:
					fs = MathFraction.Zero;
					ds = 0D;
					for (int i = 1; i <= loops; i++)
					{
						f1 = new MathFraction(i, i + 1);
						fs = fs + f1;
						ds += f1.ValueD;
					}
					line = "Součet řady zlomků (1/2 + 2/3 + 3/4 ...) v počtu " + loops + " zlomků = " + fs + " (=" + fs.ValueD + ", má být " + ds + ")";
					break;
				case 2:
					fs = MathFraction.Zero;
					ds = 0D;
					for (int i = 1; i <= loops; i++)
					{
						f1 = new MathFraction(1, i + 1);
						fs = fs + f1;
						ds += f1.ValueD;
					}
					line = "Součet řady zlomků (1/2 + 1/3 + 1/4 ...) v počtu " + loops + " zlomků = " + fs + " (=" + fs.ValueD + ", má být " + ds + ")";
					break;
				default:
					line = "Neznámý režim = " + mode;
					break;

			}
			float timeS = StopWatchStop();
			line += "; přesnost = " + maxPrecission + "; čas = " + timeS + " milisekund.";
			return line;
		}
		#endregion
	}
	#endregion
}
